#include "GameLogic.h"
#include "MoveListener.h"
#include "JumpListener.h"
#include "DeathListener.h"
#include "CollisionListener.h"
#include "LinearMath\btTransform.h"
#include "GameApp.h"
#include "Animator.h"
#include "rapidxml.hpp"
#include "blobb.h"
#include "RewardOrb.h"
#include "MirrorBoss.h"
#include "LittleFighter.h"
#include "GravityBlobb.h"
#include "HedgehogBlobb.h"
#include "LevelLoader.h"
#include "Sword.h"
#include "RemovePSProcess.h"

static const float PI = 3.14159265;

//-------------------------------------------------------------------------------------
GameLogic::GameLogic(void)
{
	mpView = 0;
	mLoadSpawnPoint = false;
	mpPhysics = new GamePhysics();
	mpProcessMgr = new GameProcessManager();
	mpUpdatePairs = AttachedObjectUpdaterPtr(new AttachedObjectUpdater());
	mpSaveFile = new SettingsManager("saves/savegame.xml", "savefile", true);
}
//-------------------------------------------------------------------------------------
GameLogic::~GameLogic(void)
{
	deleteGame();
	if (strcmp(mpSaveFile->getSetting("Saved").c_str(), "true"))
	{
		delete mpSaveFile;
		std::remove("saves/savegame.xml");
	}
	else
	{
		delete mpSaveFile;
	}
	delete mpProcessMgr;
	delete mpPhysics;
}
//-------------------------------------------------------------------------------------
bool GameLogic::init(GameView *view, bool loading)
{
	nameCounter = 0;
	mpView = view;
	mpPhysics->init(view->getSceneManager());

	loadCharacter(!loading);
	loadLevel(mpPlayer->getCurrentLevel());

	mpView->getCamera()->init(mpPlayer);

	// Create and add listener to the event manager
	EventListenerPtr moveListener(new MoveListener());
	EventListenerPtr colListener(new CollisionListener());
	EventListenerPtr jumpListener(new JumpListener());
	EventListenerPtr deathListener(new DeathListener());
	gpApp->mpEventManager->addListener(moveListener, EVENT_MOVE);
	gpApp->mpEventManager->addListener(colListener, EVENT_COLLISION);
	gpApp->mpEventManager->addListener(jumpListener, EVENT_JUMP);
	gpApp->mpEventManager->addListener(deathListener, EVENT_DEATH);

	return true;
}
//-------------------------------------------------------------------------------------
void GameLogic::deleteGame(void)
{
	BaseObjectList::iterator obj;

	//Delete Attachments
	mpUpdatePairs->clearList();

	// Delete player
	mpPlayer->kill();
	mpPlayer.reset();

	// Delete all objects in the object list
	BaseObjectList::iterator it = mBaseObjectList.begin();
	BaseObjectList::iterator end = mBaseObjectList.end();
	for (;it != end; ++it)
	{
		(*it)->kill();
	}
	mBaseObjectList.clear();

	for (list<VisibleObjectPtr>::iterator it = mVisualList.begin();it != mVisualList.end();++it)
	{
		(*it)->getEntity()->setVisible(false);
	}
	mVisualList.clear();

	// Delete all objects in the gravity list
	mGravityObjectList.clear();

	mSnapToRailOnlyList.clear();

	// Delete all triggers
	BaseTriggerList::iterator it2 = mBaseTriggerList.begin();
	BaseTriggerList::iterator end2 = mBaseTriggerList.end();
	for (;it2 != end2; ++it2)
	{
		(*it2)->kill();
	}
	mBaseTriggerList.clear();
	
	// Delete particles
	std::list<Ogre::SceneNode*>::iterator it3 = mParticleList.begin();
	std::list<Ogre::SceneNode*>::iterator end3 = mParticleList.end();
	for (;it3 != end3; ++it3)
	{
		removeParticleSystem(*it3);
	}
	mParticleList.clear();
}
//-------------------------------------------------------------------------------------
void GameLogic::reset(const bool rs)
{
	mLoadSpawnPoint = false;
	if (rs) mpSaveFile->setSetting("Saved", "false");
	else mpSaveFile->setSetting("Saved", "true");
	deleteGame();

	// Delete all sended events
	gpApp->mpEventManager->emptyQueues();

	loadCharacter(rs);
	loadLevel(mpPlayer->getCurrentLevel());

	mpView->getCamera()->init(mpPlayer);
}
//-------------------------------------------------------------------------------------
void GameLogic::loadLevel(const unsigned int level)
{
	switch (level)
	{
		case 0: 
			addLevel(LevelLoader::loadLevel("Scenes/tutorial.scene"));
			break;
		case 1: 
			addLevel(LevelLoader::loadLevel("Scenes/level_1.scene"));
			break;
		case 12: 
			addLevel(LevelLoader::loadLevel("Scenes/level_2.scene"));
			break;
		case 13: 
			addLevel(LevelLoader::loadLevel("Scenes/level_3.scene"));
			break;
		default:
			addLevel(LevelLoader::loadLevel("Scenes/tutorial.scene"));
			mpPlayer->setCurrentLevel(0);
			mpPlayer->setLevelPower(mpPlayer->getLevelPower() + 1);
			break;
	}
	if(mpPlayer->getSkillTree()->archetype == ARCHE_MELEE)
	{
		mpPlayer->equipSword();
	} else if(mpPlayer->getSkillTree()->archetype == ARCHE_RANGED)
	{
		mpPlayer->equipGun();
	}
}
//-------------------------------------------------------------------------------------
void GameLogic::loadNextLevel(void)
{
	mpPlayer->setCurrentLevel(mpPlayer->getCurrentLevel() + 1);
	saveSaveGame(0.0f, 0.0f, 0.0f, false);
	reset(false);
}
//-------------------------------------------------------------------------------------
void GameLogic::loadCharacter(const bool reset)
{
	mpPlayer = PlayerPtr(new Player());
	addObject(mpPlayer);

	mpPlayer->setSound(SOUND_MOVE, gpApp->mpAudio->playSound(SOUND_MOVE, true, mpPlayer));
	gpApp->mpAudio->stopSound(mpPlayer->getSound(SOUND_MOVE));

	mpPlayer->equipShield();
	mpPlayer->lookRight();

	if (!reset) loadSaveGame();
	else mpSaveFile->setSetting("Saved", "false");
}
//-------------------------------------------------------------------------------------
ObjectPtr GameLogic::createObject(const ObjectType type)
{
	ObjectPtr obj = mCopyMap[type];

	switch (type)
	{
	case OBJECT_REWARDORB:
		if (obj) obj = orbPtr(new rewardOrb(boost::static_pointer_cast<rewardOrb>(obj)));
		else
		{
			orbPtr rOrb(new rewardOrb(REW_HEALTH, 0.0f));
			mCopyMap[OBJECT_REWARDORB] = rOrb;
			obj = orbPtr(new rewardOrb(boost::static_pointer_cast<rewardOrb>(rOrb)));
		}
	case OBJECT_BLOBB:
		// TODO: create missing copy constructors
		/*if (obj) obj = blobbPtr(new blobb(boost::static_pointer_cast<blobb>(obj)));
		else
		{
			blobbPtr blobb(new blobb());
			mCopyMap[OBJECT_BLOBB] = blobb;
		}*/
	default:
		break;
	}

	return obj;
}
//-------------------------------------------------------------------------------------
void GameLogic::addObject(BaseObjectPtr object)
{
	switch(object->getType())
	{
	case OBJECT_REWARDORB:
		mpView->createVisibleObject(object);
		mpPhysics->addBody(object);
		mBaseObjectList.push_back(object);
		break;
	case OBJECT_MISSILE:
		mpView->createVisibleObject(object);
		mpPhysics->addBody(object);
		mSnapToRailOnlyList.push_back(object);
		mBaseObjectList.push_back(object);
		break;
	default:
		mpView->createVisibleObject(object);
		mpPhysics->addBody(object);
		mGravityObjectList.push_back(object);
		mBaseObjectList.push_back(object);
	}

	if (object->hasType(OBJECT_ENEMY)) object->scalePower(mpPlayer->getLevelPower());
}
//-------------------------------------------------------------------------------------
void GameLogic::loadSaveGame(void)
{
	if (strcmp(mpSaveFile->getSetting("Saved").c_str(), "true")) return;

	// Load position
	btTransform &trans = mpPlayer->getBody()->getWorldTransform();
	trans.setOrigin(btVector3((float)atof(mpSaveFile->getSetting("Position/x").c_str()),
							  (float)atof(mpSaveFile->getSetting("Position/y").c_str()),
							  (float)atof(mpSaveFile->getSetting("Position/z").c_str())));

	// load basis
	
	trans.setBasis(btMatrix3x3((float)atof(mpSaveFile->getSetting("Basis/xx").c_str()),
							   (float)atof(mpSaveFile->getSetting("Basis/xy").c_str()),
							   (float)atof(mpSaveFile->getSetting("Basis/xz").c_str()),
							   (float)atof(mpSaveFile->getSetting("Basis/yx").c_str()),
							   (float)atof(mpSaveFile->getSetting("Basis/yy").c_str()),
							   (float)atof(mpSaveFile->getSetting("Basis/yz").c_str()),
							   (float)atof(mpSaveFile->getSetting("Basis/zx").c_str()),
							   (float)atof(mpSaveFile->getSetting("Basis/zy").c_str()),
							   (float)atof(mpSaveFile->getSetting("Basis/zz").c_str())));

	// Load stats
	mpPlayer->getSkillTree()->healthTree = (float)atof(mpSaveFile->getSetting("Stats/end").c_str());
	mpPlayer->getSkillTree()->energyTree = (float)atof(mpSaveFile->getSetting("Stats/vit").c_str());
	mpPlayer->getSkillTree()->combatTree = (float)atof(mpSaveFile->getSetting("Stats/pow").c_str());
	mpPlayer->getSkillTree()->archetype = (Archetype)atoi(mpSaveFile->getSetting("Stats/arch").c_str());
	mpPlayer->validateStats();
	// TODO: maybe load other stuff like mPrepareTime

	// Load progress
	mpPlayer->setEXP((float)atof(mpSaveFile->getSetting("Progress/exp").c_str()));
	mpPlayer->setSkillPoints((float)atof(mpSaveFile->getSetting("Progress/sp").c_str()));

	// Load current level
	mpPlayer->setCurrentLevel(atoi(mpSaveFile->getSetting("Progress/level").c_str()));
	// Start at spawn point?
	mLoadSpawnPoint = 0 != atoi(mpSaveFile->getSetting("Progress/spawn").c_str());
}
//-------------------------------------------------------------------------------------
void GameLogic::saveSaveGame(const float x, const float y, const float z, const bool spawn)
{
	//frontout << "GAME SAVED" << endl;

	btTransform &trans = mpPlayer->getBody()->getWorldTransform();
	std::ostringstream toString;

	// Save position
    toString << x;
	mpSaveFile->setSetting("Position/x", toString.str());
	toString.str("");
	toString << y;
	mpSaveFile->setSetting("Position/y", toString.str());
	toString.str("");
	toString << z;
	sysout << z << endl;
	mpSaveFile->setSetting("Position/z", toString.str());
	toString.str("");

	// save basis (TODO: maybe change row and column
	btMatrix3x3 &mat = mpPlayer->getBody()->getWorldTransform().getBasis();
	toString << mat[0][0];
	mpSaveFile->setSetting("Basis/xx", toString.str());
	toString.str("");
	toString << mat[1][0];
	mpSaveFile->setSetting("Basis/xy", toString.str());
	toString.str("");
	toString << mat[2][0];
	mpSaveFile->setSetting("Basis/xz", toString.str());
	toString.str("");
	toString << mat[0][1];
	mpSaveFile->setSetting("Basis/yx", toString.str());
	toString.str("");
	toString << mat[1][1];
	mpSaveFile->setSetting("Basis/yy", toString.str());
	toString.str("");
	toString << mat[2][1];
	mpSaveFile->setSetting("Basis/yz", toString.str());
	toString.str("");
	toString << mat[0][2];
	mpSaveFile->setSetting("Basis/zx", toString.str());
	toString.str("");
	toString << mat[1][2];
	mpSaveFile->setSetting("Basis/zy", toString.str());
	toString.str("");
	toString << mat[2][2];
	mpSaveFile->setSetting("Basis/zz", toString.str());
	toString.str("");

	// Save stats
	toString << mpPlayer->getSkillTree()->healthTree;
	mpSaveFile->setSetting("Stats/end", toString.str());
	toString.str("");
	toString << mpPlayer->getSkillTree()->energyTree;
	mpSaveFile->setSetting("Stats/vit", toString.str());
	toString.str("");
	toString << mpPlayer->getSkillTree()->combatTree;
	mpSaveFile->setSetting("Stats/pow", toString.str());
	toString.str("");
	toString << (int)mpPlayer->getSkillTree()->archetype;
	mpSaveFile->setSetting("Stats/arch", toString.str());
	toString.str("");
	// TODO: maybe save other stuff like mPrepareTime

	// Save progress
	toString << mpPlayer->getEXP();
	mpSaveFile->setSetting("Progress/exp", toString.str());
	toString.str("");
	toString << mpPlayer->getSkillPoints();
	mpSaveFile->setSetting("Progress/sp", toString.str());
	toString.str("");
	toString << mpPlayer->getCurrentLevel();
	mpSaveFile->setSetting("Progress/level", toString.str());
	toString.str("");
	toString << spawn;
	mpSaveFile->setSetting("Progress/spawn", toString.str());
	toString.str("");

	mpSaveFile->setSetting("Saved", "true");
	gpGUI->setSaveExists(true);
}
//-------------------------------------------------------------------------------------
void GameLogic::update(const unsigned long elapsedTime)
{
	mpPhysics->update(elapsedTime);
	BaseObjectList::iterator obj;
	mpProcessMgr->update(elapsedTime);
	// Apply-methods should be done in one function for better performance
	BaseObjectList::iterator it = mBaseObjectList.begin();
	BaseObjectList::iterator end = mBaseObjectList.end();
	for (;it != end;)
	{
		obj = it;
		++it;

		if ((*obj)->dead())
		{
			mBaseObjectList.erase(obj);
			continue;
		}
		(*obj)->update(elapsedTime);
	}
	//applying the games physics
	it = mGravityObjectList.begin();
	end = mGravityObjectList.end();
	for (;it != end;)
	{
		obj = it;
		++it;

		if ((*obj)->dead())
		{
			mGravityObjectList.erase(obj);
			continue;
		}

		if ((*obj)->getBulletInitData()->mass != 0.0f)
		{
			mpPhysics->snapToRail((*obj));
			mpPhysics->applyGravity((*obj));
			mpPhysics->applyOrientation((*obj));
			//limit maxspeed of object

			btVector3 vel = (*obj)->getBody()->getLinearVelocity();
			if(vel.length() > 30.f)
				(*obj)->getBody()->setLinearVelocity(vel.normalized() * 30.f);
		}
	}
	// apply only snapToRail
	it = mSnapToRailOnlyList.begin();
	end = mSnapToRailOnlyList.end();
	for (;it != end;)
	{
		obj = it;
		++it;

		if ((*obj)->dead())
		{
			mSnapToRailOnlyList.erase(obj);
			continue;
		}

		mpPhysics->snapToRail((*obj));
	}
	
	mpUpdatePairs->updateObjects();
	//mpPlayer->update(elapsedTime);
	mpView->getCamera()->update(elapsedTime);
}
//-------------------------------------------------------------------------------------
GamePhysics* GameLogic::getPhysics(void) const
{
	return mpPhysics;
}
//-------------------------------------------------------------------------------------
GameProcessManager *GameLogic::getProcessManager(void) const
{
	return mpProcessMgr;
}
//-------------------------------------------------------------------------------------
const PlayerPtr GameLogic::getCharacter(void) const
{
	return mpPlayer;
}
//-------------------------------------------------------------------------------------
const AttachedObjectUpdaterPtr GameLogic::getUpdatePairs(void) const
{
	return mpUpdatePairs;
}
//-------------------------------------------------------------------------------------
void GameLogic::addLevel(Level &lvl)
{
	for (list<VisibleObjectPtr>::iterator it = lvl.getObjectList().begin();it != lvl.getObjectList().end();++it)
	{
		mpPlayer->mFlyingTime = 0;
		switch((*it)->getType())
		{
		case OBJECT_VISUAL:
			mpView->createVisibleObject(*it);
			mVisualList.push_front(*it);
			break;
		default:
			BaseObjectPtr obj = boost::static_pointer_cast<BaseObject>(*it);
			mpView->createVisibleObject(*it);
			mpPhysics->addBody(obj);

			mBaseObjectList.push_back(boost::static_pointer_cast<BaseObject>(*it));
		}
	}
	for (list<Spawn>::iterator it = lvl.getSpawns().begin();it != lvl.getSpawns().end();++it)
	{
		mpPlayer->mFlyingTime = 0;
		BaseObjectPtr newEnemy;
		stringstream s;
		stringstream col_stream;
		switch((*it).second)
		{
		case OBJECT_BLOBB:
			s << "blobb" << gpApp->mpGameLogic->nameCounter++;
			newEnemy = blobbPtr(new blobb());
			newEnemy->getOgreInitData()->name = OStringPtr(new Ogre::String(s.str()));
			addObject(newEnemy);
			newEnemy->getBody()->getWorldTransform() = (*it).first;
			break;
		case OBJECT_MIRROR_BOSS:
			s << "MirrorBoss" << gpApp->mpGameLogic->nameCounter++;
			newEnemy = MirrorBossPtr(new MirrorBoss());
			newEnemy->getOgreInitData()->name = OStringPtr(new Ogre::String(s.str()));
			col_stream << "MirrorBoss_Col" << gpApp->mpGameLogic->nameCounter++;
			newEnemy->getBulletInitData()->collisionName = OStringPtr(new Ogre::String(col_stream.str()));
			addObject(newEnemy);
			newEnemy->getBody()->getWorldTransform() = (*it).first;
			((MoveableObject*)(newEnemy.get()))->setSound(SOUND_MOVE, gpApp->mpAudio->playSound(SOUND_MOVE, true, newEnemy));
			gpApp->mpAudio->stopSound(newEnemy->getSound(SOUND_MOVE));
			break;
		case OBJECT_LITTLE_FIGHTER:
			s << "LittleFighter" << gpApp->mpGameLogic->nameCounter++;
			newEnemy = LittleFighterPtr(new LittleFighter());
			newEnemy->getOgreInitData()->name = OStringPtr(new Ogre::String(s.str()));
			col_stream << "LittleFighter_Col" << gpApp->mpGameLogic->nameCounter++;
			newEnemy->getBulletInitData()->collisionName = OStringPtr(new Ogre::String(col_stream.str()));
			addObject(newEnemy);
			newEnemy->getBody()->getWorldTransform() = (*it).first;
			((MoveableObject*)(newEnemy.get()))->setSound(SOUND_MOVE, gpApp->mpAudio->playSound(SOUND_MOVE, true, newEnemy));
			gpApp->mpAudio->stopSound(newEnemy->getSound(SOUND_MOVE));
			break;
		case OBJECT_RANGED_FIGHTER:
			s << "LittleShooter" << gpApp->mpGameLogic->nameCounter++;
			newEnemy = LittleFighterPtr(new LittleFighter());
			newEnemy->getOgreInitData()->name = OStringPtr(new Ogre::String(s.str()));
			col_stream << "LittleShooter_Col" << gpApp->mpGameLogic->nameCounter++;
			newEnemy->getBulletInitData()->collisionName = OStringPtr(new Ogre::String(col_stream.str()));
			addObject(newEnemy);
			newEnemy->getBody()->getWorldTransform() = (*it).first;
			((MoveableObject*)(newEnemy.get()))->setSound(SOUND_MOVE, gpApp->mpAudio->playSound(SOUND_MOVE, true, newEnemy));
			gpApp->mpAudio->stopSound(newEnemy->getSound(SOUND_MOVE));
			break;
		case OBJECT_GRAVITYBLOBB:
			s << "GravityBlobb" << gpApp->mpGameLogic->nameCounter++;
			newEnemy = GravityBlobbPtr(new GravityBlobb());
			newEnemy->getOgreInitData()->name = OStringPtr(new Ogre::String(s.str()));
			addObject(newEnemy);
			newEnemy->getBody()->getWorldTransform() = (*it).first;
			break;
		case OBJECT_HEDGEHOGBLOBB:
			newEnemy = HedgehogBlobbPtr(new HedgehogBlobb());
			s << "HedgehogBlobb" << gpApp->mpGameLogic->nameCounter++;
			newEnemy->getOgreInitData()->name = OStringPtr(new Ogre::String(s.str()));
			col_stream << "Blobb.mesh";
			newEnemy->getOgreInitData()->meshName = OStringPtr(new Ogre::String(col_stream.str()));
			addObject(newEnemy);
			newEnemy->getBody()->getWorldTransform() = (*it).first;
			break;
		default:
			s << "blobb" << gpApp->mpGameLogic->nameCounter++;
			newEnemy = blobbPtr(new blobb());
			newEnemy->getOgreInitData()->name = OStringPtr(new Ogre::String(s.str()));
			addObject(newEnemy);
			newEnemy->getBody()->getWorldTransform() = (*it).first;
		}
		//if(newEnemy)
		//{
		//	addObject(newEnemy);
		//	newEnemy->getBody()->getWorldTransform() = (*it).first;
		//	((MoveableObject*)(newEnemy.get()))->setSound(SOUND_MOVE, gpApp->mpAudio->playSound(SOUND_MOVE, true, newEnemy));
		//}
	}
	lvl.getObjectList().clear();
	mpPlayer->getBody()->setWorldTransform(btTransform(lvl.getStart()));
}
//-------------------------------------------------------------------------------------
Ogre::SceneNode *GameLogic::createParticleSystem(const Ogre::String script, Ogre::Vector3 &pos, const bool leveldel)
{
	stringstream s;
	s << "particlesystem" << gpApp->mpGameLogic->nameCounter++;

	Ogre::SceneManager *sm = mpView->getSceneManager();
	Ogre::ParticleSystem *particle = sm->createParticleSystem(s.str(), script);
	Ogre::SceneNode *particleNode = sm->getRootSceneNode()->createChildSceneNode(pos);
	particleNode->attachObject(particle);

	if (leveldel) mParticleList.push_back(particleNode);

	return particleNode;
}
//-------------------------------------------------------------------------------------
Ogre::SceneNode *GameLogic::createParticleSystem(const Ogre::String script, const unsigned long lifeDuration, Ogre::Vector3 &pos)
{
	stringstream s;
	s << "particlesystem" << gpApp->mpGameLogic->nameCounter++;

	Ogre::SceneManager *sm = mpView->getSceneManager();
	Ogre::ParticleSystem *particle = sm->createParticleSystem(s.str(), script);
	Ogre::SceneNode *particleNode = sm->getRootSceneNode()->createChildSceneNode(pos);
	particleNode->attachObject(particle);
	
	GameProcessPtr proc(new RemovePSProcess(lifeDuration, particleNode, mpView));
	mpProcessMgr->attach(proc);

	return particleNode;
}
//-------------------------------------------------------------------------------------
void GameLogic::removeParticleSystem(Ogre::SceneNode *node)
{
	mpView->getSceneManager()->getRootSceneNode()->removeAndDestroyChild(node->getName());
}
//-------------------------------------------------------------------------------------
void GameLogic::removeParticleSystem(Ogre::SceneNode *node, const unsigned long duration)
{
	Ogre::ParticleSystem *particle = (Ogre::ParticleSystem*)(node->getAttachedObject(0));
	particle->setEmitting(false);

	GameProcessPtr proc(new RemovePSProcess(duration, node, mpView));
	mpProcessMgr->attach(proc);
}